#pragma once
#include <tchar.h>
#include <afxwin.h>
#include "sqlite3/sqlite3.h"
#include "sqlite3/md5/md5.h"
#include "CSQLite/CSQLiteStmt.h"
#pragma comment( lib, "sqlite3/sqlite3.lib" )

class CSQLite {
	protected:
		::sqlite3* m_pDB;
		unsigned long long m_QueriesCount;

	public:
		/**
		 * Some construction / destruction stuff
		 */
		CSQLite( void ) {
			this->m_pDB = NULL;
			this->m_QueriesCount = 0;
		}

		CSQLite( const TCHAR* filename ) {
			this->m_QueriesCount = 0;
			this->Open( filename );
		}

		~CSQLite( void ) {
			this->Close();
		}

		/**
		 * Let's roll
		 */
		int Open( const TCHAR* filename ) {
			int r = 0;
			#ifdef UNICODE
				r = ::sqlite3_open16( filename, &this->m_pDB );
				::sqlite3_create_function16( this->m_pDB, L"md5",  1, SQLITE_UTF8, NULL, &CSQLite::__MD5, NULL, NULL );
				::sqlite3_create_function16( this->m_pDB, L"fuck", 1, SQLITE_UTF8, NULL, &CSQLite::__FUCK, NULL, NULL );
			#else
				r = ::sqlite3_open( filename, &this->m_pDB );
				::sqlite3_create_function( this->m_pDB, "md5",  1, SQLITE_UTF8, NULL, &CSQLite::__MD5, NULL, NULL );
				::sqlite3_create_function( this->m_pDB, "fuck", 1, SQLITE_UTF8, NULL, &CSQLite::__FUCK, NULL, NULL );
			#endif
			return r;
		}

		/**
		 * Do a barrel roll
		 */
		int Close( void ) {
			if( this->m_pDB ) {
				int r = ::sqlite3_close( this->m_pDB );
				this->m_pDB = NULL;
				return r;
			}
			return 0;
		}

		/**
		 * Get Queries count
		 */
		unsigned long long GetQueriesCount( void ) const {
			return this->m_QueriesCount;
		}

		/**
		 * Error handling stuff
		 */
		const TCHAR* GetErrorText( void ) const {
			#ifdef UNICODE
				return (TCHAR*)::sqlite3_errmsg16( this->m_pDB );
			#else
				return ::sqlite3_errmsg( this->m_pDB );
			#endif
		}
		
		int GetErrorCode( void ) const {
			return ::sqlite3_errcode( this->m_pDB );
		}

		/**
		 * Query handling
		 */
		CSQLiteStmt Query( const TCHAR* query ) {
			::sqlite3_stmt* pStmt = NULL;
			TCHAR autoStep[][16] = { _T("INSERT"), _T("UPDATE"), _T("DELETE"), _T("CREATE"), _T("SET"), _T("ALTER"), _T("DROP") };

			this->m_QueriesCount++;
			int result = 0;

			TRACE( _T("CSQLite.h query: %s\n"), query );
			TRACE( _T("error: %s (%d)\n"), this->GetErrorText(), this->GetErrorCode() );

			#ifdef UNICODE
				result = ::sqlite3_prepare16_v2( this->m_pDB, query, -1, &pStmt, NULL );
			#else
				result = ::sqlite3_prepare_v2( this->m_pDB, query, -1, &pStmt, NULL );
			#endif

			for( int j = 0; j < sizeof(autoStep) / sizeof(autoStep[0]); j++ ) {
				if( _tcsstr( query, autoStep[j] ) == query ) {
					::sqlite3_step( pStmt );
					::sqlite3_finalize( pStmt );
					return CSQLiteStmt( NULL );
				}
			}

			if( result != SQLITE_OK )
				return CSQLiteStmt( NULL );
			return CSQLiteStmt( pStmt );
		}
		
		/**
		 * Printf-style formatted query
		 * Example:
		 *		db->QueryF( _T("SELECT * FROM `%s` WHERE `id` > %d"), _T("lib_books"), 10 );
		 */
		CSQLiteStmt QueryF( const TCHAR* query, ... ) {
			va_list vl;
			va_start( vl, query );
			CString s, rs, tmp;
			for( unsigned int i = 0; i < _tcslen(query); i++ ) {
				if( query[i] != '%' ) {
					s += query[i];
					continue;
				}

				// make it printf-style
				tmp = _T("");
				for( ; i < _tcslen(query); i++ ) {
					tmp += query[i];
					if( query[i] == 'f' || query[i] == 'd' || query[i] == 'x' || query[i] == 'p' || query[i] == 'X' || query[i] == 's' ) {
						break;
					}
				}

				switch( query[i] ) {
					case 'f':
						rs.Format( tmp, va_arg( vl, double ) );
						break;
					case 'd':
					case 'x':
					case 'X':
						rs.Format( tmp, va_arg( vl, long ) );
						break;
					case 's':
					case 'p':
						rs.Format( tmp, va_arg( vl, void* ) );
						break;
				}
				s += rs;
			}
			return this->Query( s );
		}

		/**
		 * Make String Safe
		 */
		CString EscapeString( const TCHAR* str ) {
			CString r;
			for( DWORD i = 0; i < _tcslen( str ); i++ ) {
				switch( str[i] ) {
					case '\'':
					case '"':
						if( !i ) {
							r += '\\';
							r += str[i];
							break;
						}
						if( str[i-1] == '\\' ) {
							r += str[i];
						}
						break;
					default:
						r += str[i];
				}
			}
			return r;
		}

		/**
		 * SQL MD5() function
		 */
		static void __MD5( sqlite3_context *context, int argc, sqlite3_value **argv ) {
			MD5 md5;
			switch( sqlite3_value_type(argv[0]) ) {
				case SQLITE_INTEGER: {
					long long int iVal = sqlite3_value_int64( argv[0] );
					CStringA s;
					s.Format( "%d", sqlite3_value_int64( argv[0] ) );
					#if UNICODE
						sqlite3_result_text16( context, (const WCHAR*)CString( md5.digestString( (char*)(const char*)s ) ), -1, SQLITE_TRANSIENT );
					#else
						sqlite3_result_text( context, md5.digestString( (char*)(const char*)s ), -1, SQLITE_TRANSIENT );
					#endif
					break;
				}
				case SQLITE_NULL: {
					sqlite3_result_null( context );
					break;
				}
				case SQLITE_TEXT: {
					char* val = (char*)sqlite3_value_text( argv[0] );
					#if UNICODE
						sqlite3_result_text16( context, (const WCHAR*)CString( md5.digestString( val ) ), -1, SQLITE_TRANSIENT );
					#else
						sqlite3_result_text( context, md5.digestString( val ), -1, SQLITE_TRANSIENT );
					#endif
					break;
				}
				default: {
					sqlite3_result_null( context );
					break;
				}
			}
		}

		/**
		 * SQL FUCK()
		 */
		static void __FUCK( sqlite3_context *context, int argc, sqlite3_value **argv ) {
			#if UNICODE
				sqlite3_result_text16( context, L"FUCK YOU", -1, SQLITE_TRANSIENT );
			#else
				sqlite3_result_text( context, "FUCK YOU", -1, SQLITE_TRANSIENT );
			#endif
		}
};